package controllers

import (
	"fmt"
	"gateway-api/constants"
	pbDemo "gateway-api/proto/demo"
	"gateway-api/tools"
	"log"
	"strconv"

	"github.com/dtm-labs/client/dtmcli"
	"github.com/dtm-labs/client/dtmgrpc"
	"github.com/gin-gonic/gin"
	"github.com/lithammer/shortuuid/v3"
)

/*
 +----------------------------------------------------------------------
 + Title        : SagaController
 + Author       : huwl
 + Version      : 2.0.0
 + Initial-Time : 2020-11-23
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : DTM saga 分布式事务控制层：工作量少，并且能够解决绝大部分业务的需求。
 +				: 其核心思想是将长事务拆分为多个短事务，由Saga事务协调器协调，
 +				: 如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
 + 		        : 总结:
 +				:	saga则是异步执行子事务一次性提交事务,没有等待执行结果,要看结果需要到DTM的ui界面才能得知
 +				:	tcc同步执行的执行一个子事务就等待返回一个结果
 +				: 	无论执行成功或者失败无法在程序执行的结果得知,只能看DTM的ui界面才能得知
 +				:
 +				:
 + SUCCESS      : 表示成功
 + FAILURE      : 表示失败，这个失败是指确定的失败结果，不需要重试。
                  例如子事务因转账余额不足而失败，dtm服务因为事务已完成不允许再次提交等
 + ONGOING      : 表示未完成，还在正常进行中，此时dtm服务器需要采用固定间隔重试，而不是指数退避算法重试
				  对tcc不管用,在tcc模式下和FAILURE状态一样

 + other status : 表示临时错误，采用指数退避算法重试，避免出现故障或者bug，导致大量重试，导致负载过高
 + SUCCESS http : 状态码 200； grpc： err == nil
 + FAILURE http : 状态码 409 (http.StatusConflict)；grpc Code == Aborted tcc 失败、未完成的状态必须使用409、425
 + ONGOING http : 状态码 425 (http.StatusTooEarly)；grpc Code == FailedPrecondition tcc 失败、未完成的状态必须使用409、425
 +----------------------------------------------------------------------
*/
type SagaController struct {
	BaseController
}

type ReqHTTP struct {
	Uid            int    `json:"uid"`
	Amount         int    `json:"amount"`
	TransInResult  string `json:"trans_in_result"`
	TransOutResult string `json:"trans_out_result"`
	Store          string `json:"store"` // default mysql, value can be mysql|redis
}

/**
 * saga grpc 方式
 * 特别说明: 如果微服务使用的是go-micro框架，DTM将无法调用子事务原因不明，大概是go-micro对grpc进行改写
 * 如果项目使用的是go-micro框架解决方案：1.0 将微服务中需要使用DTM的服务方法添加对应的http方法，然后使用 DTM http 方式。
 *									  2.0 在微服务中添加google grpc服务详见，本服务
 * @return Json
 * @author huwl
 */
func (control SagaController) SagaGrpc(ctx *gin.Context) {
	amount, _ := strconv.Atoi(ctx.DefaultQuery("amount", "30"))
	transInResult := ctx.DefaultQuery("trans_in_result", "")
	transOutResult := ctx.DefaultQuery("trans_out_result", "")
	req := &pbDemo.ReqGrpc{
		Amount:         int64(amount),
		TransInResult:  transInResult,
		TransOutResult: transOutResult,
		// 表示失败，这个失败是指确定的失败结果，不需要重试。
		// TransInResult: "FAILURE",
		// 表示未完成，还在正常进行中，此时dtm服务器需要采用固定间隔重试，而不是指数退避算法重试
		// TransInResult: "ONGOING",
	}

	// 获取微服务地址
	target, err := tools.GetServiceAddress(tools.ServiceName)
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	// 执行方式一: 提交事务 -> 执行转出成功 -> 执行转入成功 -> 全局事务成功
	// 执行方式二: 提交事务 -> 执行转出成功 -> 执行转入失败 -> 执行补偿转入 -> 执行补偿转出 -> 全局事务失败
	saga := dtmgrpc.NewSagaGrpc(tools.DtmGrpcServer, shortuuid.New()).
		Add(target+"/demo.DemoSrv/TransOut", target+"/demo.DemoSrv/TransOutRevert", req).
		Add(target+"/demo.DemoSrv/TransIn", target+"/demo.DemoSrv/TransInRevert", req)

	err = saga.Submit()
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	control.BaseSuccess(ctx, control.WithData(gin.H{
		// "gid": saga.Gid,
	}))
}

/**
 * saga http 方式
 *
 * @return Json
 * @author huwl
 */
func (control SagaController) SagaHttp(ctx *gin.Context) {
	amount, _ := strconv.Atoi(ctx.DefaultQuery("amount", "30"))
	transInResult := ctx.DefaultQuery("trans_in_result", "")
	transOutResult := ctx.DefaultQuery("trans_out_result", "")
	req := &ReqHTTP{
		Amount:         amount,
		TransInResult:  transInResult,
		TransOutResult: transOutResult,
		// TransInResult: "FAILURE",
		// TransInResult: "ONGOING",
	}
	// 执行方式一: 提交事务 -> 执行转出成功 -> 执行转入成功 -> 全局事务成功
	// 执行方式二: 提交事务 -> 执行转出成功 -> 执行转入失败 -> 执行补偿转入 -> 执行补偿转出 -> 全局事务失败

	fmt.Println("DtmHttpServer =", tools.DtmHttpServer)
	fmt.Println("HttpServiceUrl =", tools.HttpServiceUrl)
	saga := dtmcli.NewSaga(tools.DtmHttpServer, shortuuid.New()).
		Add(tools.HttpServiceUrl+"/saga/transOut", tools.HttpServiceUrl+"/saga/transOutRevert", req).
		Add(tools.HttpServiceUrl+"/saga/transIn", tools.HttpServiceUrl+"/saga/transInRevert", req)

	err := saga.Submit()
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	control.BaseSuccess(ctx, control.WithData(gin.H{
		"gid": saga.Gid,
	}))
}

/**
 * Saga 转出
 *
 * @param string trans_out_result ---------------------------必传,转出结果
 * @return Json
 * @author huwl
 */
func (control SagaController) TransOut(ctx *gin.Context) {
	result := ctx.DefaultPostForm("trans_out_result", "")

	if result == "FAILURE" {
		// 返回409终止状态
		control.BaseError(ctx, control.WithStatusCode(constants.ResultFailure), control.WithData("TransOut code=409 FAILURE"))
		return
	} else if result == "ONGOING" {
		// 返回表示未完成
		control.BaseError(ctx, control.WithStatusCode(constants.ResultOngoing), control.WithData("TransOut code=425 ONGOING"))
		return
	}

	log.Println("================ TransOut ================")
	control.BaseSuccess(ctx, control.WithData("TransOut SUCCESS"))
}

/**
 * Saga 补偿转出
 *
 * @return Json
 * @author huwl
 */
func (control SagaController) TransOutRevert(ctx *gin.Context) {
	log.Println("================ TransOutRevert ================")
	control.BaseSuccess(ctx, control.WithData("TransOutRevert"))
}

/**
 * Saga 转入
 *
 * @return Json
 * @author huwl
 */
func (control SagaController) TransIn(ctx *gin.Context) {
	result := ctx.DefaultPostForm("trans_in_result", "")

	// 在项目中必须在执行核心代码失败时才能使用409,参数校验失败之类的错误不能使用409
	// 因为一旦409就要执行取消、补偿操作将导致数据错乱
	// 例如: 扣除库存时执行扣除库存失败时返回409
	// 还原库存的时候最好判断一下是否真的已扣除,如何判断是否已扣除可以在
	if result == "FAILURE" {
		// 返回409终止状态
		control.BaseError(ctx, control.WithStatusCode(409), control.WithData("TransIn code=409 FAILURE"))
		return
	} else if result == "ONGOING" {
		// 返回表示未完成
		control.BaseError(ctx, control.WithStatusCode(constants.ResultOngoing), control.WithData("TransIn code=425 ONGOING"))
		return
	}

	log.Println("================ TransIn ================")
	control.BaseSuccess(ctx, control.WithData("TransIn SUCCESS"))
}

/**
 * Saga 补偿转入
 *
 * @return Json
 * @author huwl
 */
func (control SagaController) TransInRevert(ctx *gin.Context) {
	log.Println("================ TransInRevert ================")
	control.BaseSuccess(ctx, control.WithData("TransInRevert"))
}
